package drds.common.db.xml_mapping.reflection.factory;

import drds.common.db.xml_mapping.reflection.ReflectionException;
import drds.common.db.xml_mapping.reflection.Reflector;

import java.lang.reflect.Constructor;
import java.util.*;
import java.util.stream.Collectors;


public class ObjectFactoryImpl implements ObjectFactory {

  @Override
  public <T> T create(Class<T> type) {
    return create(type, null, null);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T create(Class<T> type, List<Class<?>> constructorArgumentTypeList, List<Object> constructorArgumentValueList) {
    Class<?> implementClass = resolveInterfaceToImplementClass(type);
    // we know types are assignable
    return (T) instantiateClass(implementClass, constructorArgumentTypeList, constructorArgumentValueList);
  }

  protected Class<?> resolveInterfaceToImplementClass(Class<?> type) {
    Class<?> classToCreate;
    if (type == List.class || type == Collection.class || type == Iterable.class) {
      classToCreate = ArrayList.class;
    } else if (type == Map.class) {
      classToCreate = HashMap.class;
    } else if (type == SortedSet.class) { // issue #510 Collections Support
      classToCreate = TreeSet.class;
    } else if (type == Set.class) {
      classToCreate = HashSet.class;
    } else {
      classToCreate = type;
    }
    return classToCreate;
  }

  //
  private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgumentTypeList, List<Object> constructorArgumentValueList) {
    try {
      Constructor<T> constructor;
      if (constructorArgumentTypeList == null || constructorArgumentValueList == null) {
        constructor = type.getDeclaredConstructor();
        try {
          return constructor.newInstance();
        } catch (IllegalAccessException e) {
          if (Reflector.checkPermissionOk()) {
            constructor.setAccessible(true);
            return constructor.newInstance();
          } else {
            throw e;
          }
        }
      }
      //
      constructor = type.getDeclaredConstructor(constructorArgumentTypeList.toArray(new Class[constructorArgumentTypeList.size()]));
      try {
        return constructor.newInstance(constructorArgumentValueList.toArray(new Object[constructorArgumentValueList.size()]));
      } catch (IllegalAccessException e) {
        if (Reflector.checkPermissionOk()) {
          constructor.setAccessible(true);
          return constructor.newInstance(constructorArgumentValueList.toArray(new Object[constructorArgumentValueList.size()]));
        } else {
          throw e;
        }
      }
    } catch (Exception e) {
      String constructorArgumentTypeListString = Optional.ofNullable(constructorArgumentTypeList).orElseGet(Collections::emptyList)
        .stream().map(Class::getSimpleName).collect(Collectors.joining(","));
      //
      String constructorArgumentValueListString = Optional.ofNullable(constructorArgumentValueList).orElseGet(Collections::emptyList)
        .stream().map(String::valueOf).collect(Collectors.joining(","));
      throw new ReflectionException("Error instantiating " + type + " with invalid types (" + constructorArgumentTypeListString + ") or values (" + constructorArgumentValueListString + "). Cause: " + e, e);
    }
  }


  @Override
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }

}
